﻿using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace AsynchronousMethods.Nonblocking
{
  public class Server
  {
    public void Serve (IPAddress address, int port)
    {
      ThreadPool.SetMinThreads (50, 50);
      TcpListener listener = new TcpListener (address, port);
      listener.Start ();
      while (true)
      {
        TcpClient c = listener.AcceptTcpClient ();
        ThreadPool.QueueUserWorkItem (ReverseEcho, c);
      }
    }

    void ReverseEcho (object client)
    {
      new ReverseEcho ().Begin ((TcpClient)client);
    }
  }

  class ReverseEcho
  {
    TcpClient _client;
    NetworkStream _stream;
    byte [] _data = new byte [5000];
    int _bytesRead = 0;

    internal void Begin (TcpClient c)
    {
      try
      {
        _client = c;
        _stream = c.GetStream ();
        Read ();
      }
      catch (Exception ex) { ProcessException (ex); }
    }

    void Read ()            // Read in a nonblocking fashion.
    {
      while (true)
      {
        IAsyncResult r = _stream.BeginRead
         (_data, _bytesRead, _data.Length - _bytesRead, ReadCallback, null);

        // This will nearly always return in the next line:
        if (!r.CompletedSynchronously) return;   // Handled by callback
        if (!EndRead (r)) break;
      }
      Write ();
    }

    void ReadCallback (IAsyncResult r)
    {
      try
      {
        if (r.CompletedSynchronously) return;
        if (EndRead (r))
        {
          Read ();       // More data to read!
          return;
        }
        Write ();
      }
      catch (Exception ex) { ProcessException (ex); }
    }

    bool EndRead (IAsyncResult r)   // Returns false if there’s no more data
    {
      int chunkSize = _stream.EndRead (r);
      _bytesRead += chunkSize;
      return chunkSize > 0 && _bytesRead < _data.Length;   // More to read
    }

    void Write ()
    {
      Array.Reverse (_data);
      _stream.BeginWrite (_data, 0, _data.Length, WriteCallback, null);
    }

    void WriteCallback (IAsyncResult r)
    {
      try { _stream.EndWrite (r); }
      catch (Exception ex) { ProcessException (ex); }
      Cleanup ();
    }

    void ProcessException (Exception ex)
    {
      Cleanup ();
      Console.WriteLine ("Error: " + ex.Message);
    }

    void Cleanup ()
    {
      if (_stream != null) _stream.Close ();
      if (_client != null) _client.Close ();
    }
  }
}
